package limiter

import (
	_ "context"
	"fmt"
	"net/url"
	"sync/atomic"
	"time"

	log "abm-cache-center/net/blade/logger"
	xhttp "abm-cache-center/net"
	xtime "abm-cache-center/time"
	"abm-cache-center/conf"

	"golang.org/x/time/rate"
)

const (
	_codeOk          = 0
	_codeNotModified = -304
	_limitURL        = "http://%s/x/v1/msm/limits"

	_defBurst = 100
)

// Config config.
type Config struct {
	// Domain server domain
	Domain string
	// Family app name
	Family string
	// All get all time slice
	All xtime.Duration
	// Diff get diff time slice
	Diff xtime.Duration
	//HTTPClient httpclient config
	ClientConfig *conf.HttpClient
}

// Limiter controls how frequently events are allowed to happen.
type Limiter struct {
	apps   atomic.Value
	client *xhttp.Client
	conf   *Config
}

type res struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    *data  `json:"data"`
}

type data struct {
	MD5  string `json:"md5"`
	Apps map[string]struct {
		Burst int        `json:"burst"`
		Rate  rate.Limit `json:"rate"`
	} `json:"apps"`
}

// New return Limiter.
func New(conf *Config) (l *Limiter) {
	l = &Limiter{}
	l.conf = conf
	l.client = xhttp.NewClient(conf.ClientConfig)
	l.apps.Store(make(map[string]*rate.Limiter))
	ver, err := l.update("")
	if err != nil {
		log.Error("limiter update error(%v)", err)
	}
	go l.updateproc(ver)
	return
}

// Allow reports whether event may happen at time now.
func (l *Limiter) Allow(appKey string) bool {
	ls, ok := l.apps.Load().(map[string]*rate.Limiter)
	if !ok {
		return true
	}
	lim, ok := ls[appKey]
	if !ok {
		return true
	}
	return lim.Allow()
}

func (l *Limiter) updateproc(lastVer string) {
	var (
		ver  string
		err  error
		last = time.Now()
		all  = time.Duration(l.conf.All)
		diff = time.Duration(l.conf.Diff)
	)
	if l.conf.All == 0 {
		all = time.Hour
	}
	if l.conf.Diff == 0 {
		diff = 10 * time.Minute
	}
	time.Sleep(diff)
	for {
		cur := time.Now()
		if cur.Sub(last) > all {
			if ver, err = l.update(""); err != nil {
				log.Error("limiter l.update() error(%v)", err)
				time.Sleep(10 * time.Second)
				continue
			}
			last = cur
		} else {
			if ver, err = l.update(lastVer); err != nil {
				log.Error("limiter l.update(%s) error(%v)", lastVer, err)
				time.Sleep(10 * time.Second)
				continue
			}
		}
		lastVer = ver
		time.Sleep(diff)
	}
}

func (l *Limiter) update(ver string) (hmd5 string, err error) {
	var (
		// res    string
		// bytes  []byte
		params = url.Values{}
	)
	params.Set("family", l.conf.Family)
	params.Set("md5", ver)
	if _, err = l.client.Do("GET", fmt.Sprintf(_limitURL, l.conf.Domain), params.Encode(), nil); err != nil {
		err = fmt.Errorf("limiter e.client.Get(%v) error(%v)", fmt.Sprintf(_limitURL, l.conf.Domain), err)
		return
	}
	/*
	switch res.Code {
	case _codeOk:
		if res.Data == nil {
			err = fmt.Errorf("limiter app get() response error result: %v", res)
			return
		}
	case _codeNotModified:
		return ver, nil
	default:
		err = fmt.Errorf("limiter call code %d", res.Code)
		return
	}
	if bytes, err = json.Marshal(res.Data.Apps); err != nil {
		return
	}
	mb := md5.Sum(bytes)
	if res.Data.MD5 != hex.EncodeToString(mb[:]) {
		err = fmt.Errorf("limiter get apps fail error md5")
		return
	}
	var (
		ok   bool
		lmtr *rate.Limiter
		ls   map[string]*rate.Limiter
		nls  map[string]*rate.Limiter
	)
	if ls, ok = l.apps.Load().(map[string]*rate.Limiter); !ok {
		log.Warn("limiter apps load map have no data")
		return
	}
	nls = make(map[string]*rate.Limiter)
	for k1, v1 := range ls {
		nls[k1] = v1
	}
	for key, br := range res.Data.Apps {
		if lmtr, ok = nls[key]; !ok || (lmtr.Burst() != br.Burst || lmtr.Limit() != br.Rate) {
			l := rate.Inf
			b := _defBurst
			if br.Rate > 0 {
				l = br.Rate
			}
			if br.Burst > 0 {
				b = br.Burst
			}
			nls[key] = rate.NewLimiter(l, b)
			log.Info("limiter init appkey(%s) rate(%v) burst(%d)", key, l, b)
		}
	}
	l.apps.Store(nls)
	return res.Data.MD5, nil
	*/
	return
}
